/*
 * Copyright (C) 2022 KylinSoft Co., Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */

#include "trayiconwatcher.h"
#include <QDBusReply>
#include <QDBusServiceWatcher>
#include <QDBusInterface>

TrayIconWatcher::TrayIconWatcher(QObject *parent) : QObject(parent)
{
}

QString TrayIconWatcher::iconSource()
{
    return m_iconSource;
}

void TrayIconWatcher::setIconSource(const QString &source)
{
    m_iconSource = source;
    emit iconSourceChanged();
}

int TrayIconWatcher::type()
{
    return m_type;
}

void TrayIconWatcher::setType(const int &type)
{
    m_type = type;
    switch (m_type) {
    case Volume: {
        setVolumeIconByValue(50);
        QDBusConnection::sessionBus().connect(QString("org.ukui.media"),
                                              QString("/org/ukui/media"),
                                              QString("org.ukui.media"),
                                              QString("updateMute"), this, SLOT(setMuteState(bool)));
        QDBusConnection::sessionBus().connect(QString("org.ukui.media"),
                                              QString("/org/ukui/media"),
                                              QString("org.ukui.media"),
                                              QString("updateVolume"), this, SLOT(setVolume(int)));
        getVolume();
        break;
    }
    case Power: {
        setPowerIconByValue(2, 50);
        initPowerDbusAddress();
        break;
    }
    case Notification: {
        setNotificationIconByValue(false);
        QDBusConnection::sessionBus().connect(QString("org.ukui.Sidebar"),
                                              QString("/org/ukui/Sidebar/notification"),
                                              QString("org.ukui.Sidebar.notification"),
                                              QString("Sig_NotificationNumChange"),
                                              this,
                                              SLOT(getNotificationState()));

        getNotificationState();
        break;
    }
    case Network: {
        setNetIconByValue(-1, NetType::Unconnected);
        QDBusConnection::systemBus().connect(QString("org.freedesktop.NetworkManager"),
                                             QString("/org/freedesktop/NetworkManager"),
                                             QString("org.freedesktop.DBus.Properties"),
                                             QString("PropertiesChanged"),
                                             this,
                                             SLOT(getWifiValue()));
        getWifiValue();
        break;
    }
    case Bluetooth: {
        setBluetoothIconByValue(true);
        watchBluetoothService();
        getBluetoothSwitch();
        break;
    }
    case TimeDate: {
        if (QGSettings::isSchemaInstalled("org.ukui.control-center.panel.plugins")) {
            timeSetting = new QGSettings("org.ukui.control-center.panel.plugins");
            connect(timeSetting, &QGSettings::changed, this, &TrayIconWatcher::getTimeDateState);
        }
        getTimeDateState();
    }
    default:
        break;
    }
    emit typeChanged();
}

QString TrayIconWatcher::timeformat()
{
    return m_timeformat;
}

void TrayIconWatcher::settimeformat(const QString &timeformat)
{
    if (m_timeformat != timeformat) {
        m_timeformat = timeformat;
        emit timeformatChanged();
    }
}

QString TrayIconWatcher::dateformat()
{
    return m_dateformat;
}

void TrayIconWatcher::setdateformat(const QString &dateformat)
{
    if (m_dateformat != dateformat) {
        m_dateformat = dateformat;
        emit dateformatChanged();
    }
}

void TrayIconWatcher::asyncCallDbus(QDBusInterface &interface,
                                    const QString &method,
                                    const QList<QVariant> &argList,
                                    const std::function<void(QDBusPendingCallWatcher* call)>&callBack)
{
    if (!interface.isValid()) {
        qWarning() << method << "interface not valid" << interface.lastError();
        return;
    }
    QDBusPendingCall async = interface.asyncCallWithArgumentList(method, argList);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
    QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [callBack](QDBusPendingCallWatcher* call) {
        callBack(call);
    });
}

void TrayIconWatcher::getVolume()
{
    QDBusInterface interface("org.ukui.media",
                             "/org/ukui/media",
                             "org.ukui.media",
                             QDBusConnection::sessionBus());

    asyncCallDbus(interface, "getDefaultOutputVolume",
                  QList<QVariant>(), [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<int> reply = *call;
        if (reply.isError()) {
            qWarning() << __func__ << "getDefaultOutputVolume error, " << reply.error();
            setVolumeIconByValue(0);
            return;
        }
        setVolumeIconByValue(reply.value());
        call->deleteLater();
    });
}

void TrayIconWatcher::initPowerDbusAddress()
{
    QDBusInterface interface("org.freedesktop.UPower",
                             "/org/freedesktop/UPower",
                             "org.freedesktop.UPower",
                             QDBusConnection::systemBus());

    asyncCallDbus(interface, "EnumerateDevices",
                  QList<QVariant>(), [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<QList<QDBusObjectPath>> reply = *call;
        if (reply.isError()) {
            qWarning() << "EnumerateDevices error, " << reply.error();
            return;
        }

        QStringList users;
        for (QDBusObjectPath op : reply.value()) {
             users << op.path();
        }
        if (users.count() <= 1) {
            return;
        }
        powerpath = users.at(1);
        QDBusConnection::systemBus().connect(QString("org.freedesktop.UPower"),
                                             powerpath,
                                             "org.freedesktop.DBus.Properties",
                                             "PropertiesChanged",
                                             this,
                                             SLOT(initPowerValue()));
        initPowerValue();
        call->deleteLater();
    });
}

void TrayIconWatcher::setPowerValue(const QDBusPendingReply<QVariant> percentageReply)
{
    QDBusInterface powerInterface("org.freedesktop.UPower",
                                  powerpath,
                                  "org.freedesktop.DBus.Properties",
                                  QDBusConnection::systemBus());

    asyncCallDbus(powerInterface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.UPower.Device"
                      << "State",
                  [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<QVariant> stateReply = *call;
        if (stateReply.isError()) {
            qWarning() << "Get org.freedesktop.UPower.Device.State error, " << stateReply.error();
        }

        if (percentageReply.isValid() && stateReply.isValid()) {
            setPowerIconByValue(stateReply.value().toInt(), percentageReply.value().toInt());
        }
        call->deleteLater();
    });
}

void TrayIconWatcher::getNotificationState()
{
    QDBusInterface interface("org.ukui.Sidebar",
                             "/org/ukui/Sidebar/notification",
                             "org.ukui.Sidebar.notification",
                             QDBusConnection::sessionBus());

    asyncCallDbus(interface, "getNotificationNum",
                  QList<QVariant>(), [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<int> reply = *call;
        if (reply.isError()) {
            qWarning() << "getNotificationNum error, " << reply.error();
            setNotificationIconByValue(false);
            return;
        }
        setNotificationIconByValue(reply.value());
        call->deleteLater();
    });
}

void TrayIconWatcher::initPowerValue()
{
    QDBusInterface powerInterface("org.freedesktop.UPower",
                                  powerpath,
                                  "org.freedesktop.DBus.Properties",
                                  QDBusConnection::systemBus());

    asyncCallDbus(powerInterface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.UPower.Device"
                      << "Percentage",
                  [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<QVariant> percentageReply = *call;
        if (percentageReply.isError()) {
            qWarning() << "Get org.freedesktop.UPower.Device.Percentage error, " << percentageReply.error();
        }
        setPowerValue(percentageReply);
        call->deleteLater();
    });
}

void TrayIconWatcher::getBluetoothSwitch()
{
    QDBusInterface interface("com.ukui.bluetooth",
                             "/com/ukui/bluetooth",
                             "com.ukui.bluetooth",
                             QDBusConnection::sessionBus());

    asyncCallDbus(interface, "getDefaultAdapterAddress",
                  QList<QVariant>(), [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<QString> adapterAddressReply = *call;
        if (adapterAddressReply.isError()) {
            qWarning() << "getDefaultAdapterAddress error, " << adapterAddressReply.error();
            setBluetoothIconByValue(false);
            return;
        }
        if (adapterAddressReply.value().isEmpty()) {
            setBluetoothIconByValue(true);
            return;
        }
        setBluetoothState();
        call->deleteLater();
    });
}

void TrayIconWatcher::setBluetoothState()
{
    QDBusInterface interfacePower("com.ukui.bluetooth",
                             "/com/ukui/bluetooth",
                             "com.ukui.bluetooth",
                             QDBusConnection::sessionBus());

    asyncCallDbus(interfacePower, "getDefaultAdapterPower",
                  QList<QVariant>(), [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<bool> adapterPowerReply = *call;
        if (adapterPowerReply.isError()) {
            qWarning() << "getDefaultAdapterPower error, " << adapterPowerReply.error();
            setBluetoothIconByValue(false);
            return;
        }
        setBluetoothIconByValue(adapterPowerReply.value());
        call->deleteLater();
    });
}

void TrayIconWatcher::watchBluetoothService()
{
    QDBusServiceWatcher *blueDbusWatcher = new QDBusServiceWatcher();
    blueDbusWatcher->setConnection(QDBusConnection::sessionBus());
    blueDbusWatcher->addWatchedService("com.ukui.bluetooth");
    QObject::connect(blueDbusWatcher, &QDBusServiceWatcher::serviceRegistered, [=]() {
        QDBusConnection::sessionBus().connect(QString("com.ukui.bluetooth"),
                                              QString("/com/ukui/bluetooth"),
                                              QString("com.ukui.bluetooth"),
                                              QString("defaultAdapterPowerChanged"),
                                              this,
                                              SLOT(getBluetoothSwitch()));
        getBluetoothSwitch();
        blueDbusWatcher->deleteLater();
    });
}

void TrayIconWatcher::getTimeDateState()
{
    if (QGSettings::isSchemaInstalled("org.ukui.control-center.panel.plugins")) {
        if (timeSetting->keys().contains("hoursystem")) {
            QString timeState = timeSetting->get("hoursystem").toString();
            settimeformat(timeState);
        }
        if (timeSetting->keys().contains("hoursystem")) {
            QString dateState = timeSetting->get("date").toString();
            setdateformat(dateState);
        }
    }
}

void TrayIconWatcher::getNetConnectType(const QString objPath)
{
    QDBusInterface interface("org.freedesktop.NetworkManager",
                                  objPath,
                                  "org.freedesktop.DBus.Properties",
                                  QDBusConnection::systemBus() );

    asyncCallDbus(interface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.NetworkManager.Connection.Active"
                      << "Type",
                  [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<QVariant> replyConnectType = *call;
        if (wireNetExist) {
            return;
        }
        if (replyConnectType.value().toString() == "802-3-ethernet") {
            wireNetExist = true;
            getWireNetState();
        } else if (replyConnectType.value().toString() == "802-11-wireless") {
            getWirelessNetState(objPath);
        } else {
            setNetIconByValue(-1, NetType::Unconnected);
        }
    });
}

void TrayIconWatcher::getWireNetState()
{
    QDBusInterface interface( "org.freedesktop.NetworkManager",
                              "/org/freedesktop/NetworkManager",
                              "org.freedesktop.DBus.Properties",
                              QDBusConnection::systemBus());

    asyncCallDbus(interface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.NetworkManager"
                      << "Connectivity",
                  [=](QDBusPendingCallWatcher* call) {
        QDBusPendingReply<QVariant> replyConnectivity = *call;
        int connectivityState = replyConnectivity.value().toInt();
        if (connectivityState == 4) {
            setNetIconByValue(1, NetType::Wire); //有网
            return;
        }
        setNetIconByValue(-1, NetType::Wire); //无网
    });
}

void TrayIconWatcher::getWirelessNetState(const QString objPath)
{
    QDBusInterface interface( "org.freedesktop.NetworkManager",
                              "/org/freedesktop/NetworkManager",
                              "org.freedesktop.DBus.Properties",
                              QDBusConnection::systemBus());

    asyncCallDbus(interface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.NetworkManager"
                      << "Connectivity",
                  [=](QDBusPendingCallWatcher* call) {
        if (wireNetExist) {
            return;
        }
        QDBusPendingReply<QVariant> replyConnectivity = *call;
        int connectivityState = replyConnectivity.value().toInt();
        if (connectivityState != 4) {
            setNetIconByValue(-1, NetType::Wireless);
            return;
        }
        getWirelessNetValuePath(objPath);
    });
}

void TrayIconWatcher::getWirelessNetValuePath(const QString objPath)
{
    QDBusInterface interface("org.freedesktop.NetworkManager",
                             objPath,
                             "org.freedesktop.DBus.Properties",
                             QDBusConnection::systemBus() );

    asyncCallDbus(interface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.NetworkManager.Connection.Active"
                      << "SpecificObject",
                  [=](QDBusPendingCallWatcher* call) {
        if (wireNetExist) {
            return;
        }
        QDBusPendingReply<> resultSpecificObject = *call;
        QList<QVariant> outArgsSpecificObject = resultSpecificObject.reply().arguments();
        if (outArgsSpecificObject.isEmpty()) {
            qWarning() << __func__ << "outArgsSpecificObject is empty !!!";
            return;
        }
        QVariant firstSpecificObject = outArgsSpecificObject.at(0);
        QDBusVariant dbvFirstSpecificObject = firstSpecificObject.value<QDBusVariant>();
        QVariant vFirstSpecificObject = dbvFirstSpecificObject.variant();
        QDBusObjectPath objPathSpecificObject = vFirstSpecificObject.value<QDBusObjectPath>();
        getWirelessNetValue(objPathSpecificObject.path());
    });
}

void TrayIconWatcher::getWirelessNetValue(const QString objPathSpecificObject)
{
    QDBusInterface interface( "org.freedesktop.NetworkManager",
                              objPathSpecificObject,
                              "org.freedesktop.DBus.Properties",
                              QDBusConnection::systemBus() );

    asyncCallDbus(interface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.NetworkManager.AccessPoint"
                      << "Strength",
                  [=](QDBusPendingCallWatcher* call) {
        if (wireNetExist) {
            return;
        }
        QDBusPendingReply<QVariant> replyStrength = *call;
        QByteArray sesultStrength = replyStrength.value().toByteArray();
        int wifiSignal = sesultStrength[0] & 0x000000FF;
        wifiSignal |= ((sesultStrength[1] << 8) & 0x0000FF00);
        wifiSignal |= ((sesultStrength[2] << 16) & 0x00FF0000);
        wifiSignal |= ((sesultStrength[3] << 24) & 0xFF000000);
        setNetIconByValue(wifiSignal, NetType::Wireless);
    });
}

void TrayIconWatcher::getWifiValue()
{
    QDBusInterface interface( "org.freedesktop.NetworkManager",
                              "/org/freedesktop/NetworkManager",
                              "org.freedesktop.DBus.Properties",
                              QDBusConnection::systemBus());

    asyncCallDbus(interface, "Get",
                  QList<QVariant>()
                      << "org.freedesktop.NetworkManager"
                      << "ActiveConnections",
                  [=](QDBusPendingCallWatcher* call) {
        //获取已经连接了那些网络，及这些网络对应的网络类型(ethernet or wifi)
        QDBusPendingReply<> result = *call;
        QList<QVariant> outArgs = result.reply().arguments();
        if (outArgs.isEmpty()) {
            qWarning() << __func__ << "outArgs is empty !!!";
            return;
        }
        QVariant first = outArgs.at(0);
        QDBusVariant dbvFirst = first.value<QDBusVariant>();
        QVariant vFirst = dbvFirst.variant();
        QDBusArgument dbusArgs = vFirst.value<QDBusArgument>();

        QDBusObjectPath objPath;
        dbusArgs.beginArray();
        if (dbusArgs.atEnd()) {
            setNetIconByValue(-1, NetType::Unconnected);
            return;
        }
        wireNetExist = false;
        while (!dbusArgs.atEnd()) {
            dbusArgs >> objPath;
            getNetConnectType(objPath.path());
        }
        call->deleteLater();
    });
}

void TrayIconWatcher::setVolumeIconByValue(int value)
{
    if (m_type != TrayIconWatcher::Volume) {
        return;
    }
    if (value <= 0) {
        setIconSource("audio-volume-muted-symbolic");
    } else if (value > 0 && value <= 33) {
        setIconSource("audio-volume-low-symbolic");
    } else if (value > 33 && value <= 66) {
        setIconSource("audio-volume-medium-symbolic");
    } else {
        setIconSource("audio-volume-high-symbolic");
    }
}

void TrayIconWatcher::setPowerIconByValue(int powerStateValue, int powerValue)
{
    if (m_type != TrayIconWatcher::Power) {
        return;
    }
    if (powerValue == 100) {
        if (powerStateValue == 2 || powerStateValue == 3) {
            setIconSource("battery-level-100-symbolic");
        } else {
            setIconSource("battery-level-100-charging-symbolic");
        }
        return;
    }
    int val = 0;
    for (int i = 10; i <= 100; i += 10) {
        if (powerValue <= i) {
            val = i - 10;
            break;
        }
    }
    QString valueString = "";
    if (powerStateValue == 2 || powerStateValue == 3) {
        valueString = QString("battery-level-%1-symbolic").arg(val);
    } else {
        valueString = QString("battery-level-%1-charging-symbolic").arg(val);
    }
    setIconSource(valueString);
}

void TrayIconWatcher::setNotificationIconByValue(bool value)
{
    if (m_type != TrayIconWatcher::Notification) {
        return;
    }
    if (value) {
        setIconSource("kylin-tool-box-null");
    } else {
        setIconSource("kylin-tool-box");
    }
}

void TrayIconWatcher::setNetIconByValue(int value, TrayIconWatcher::NetType netType)
{
    if (m_type != TrayIconWatcher::Network) {
        return;
    }
    if (netType == NetType::Unconnected) {
        setIconSource("network-wired-disconnected-symbolic");
        return;
    }

    if (netType == NetType::Wire) {
        if (value > 0) {
            setIconSource("network-wired-symbolic");
            return;
        }
        setIconSource("network-error-symbolic");
        return;
    }

    if (netType == NetType::Wireless) {
        if (value < 0) {
            setIconSource("network-wireless-signal-good-symbolic");
            return;
        }
        //无线网络状态改变
        if (value >= 75) {
            setIconSource("network-wireless-signal-excellent-symbolic");
        } else if (value >= 55 && value < 75) {
            setIconSource("network-wireless-signal-good-symbolic");
        } else if (value >= 35 && value < 55) {
            setIconSource("network-wireless-signal-ok-symbolic");
        } else if(value >= 15 && value < 35) {
            setIconSource("network-wireless-signal-weak-symbolic");
        } else {
            setIconSource("network-wired-disconnected-symbolic");//network-cellular-signal-none-symbolic
        }
    }
}

void TrayIconWatcher::setBluetoothIconByValue(bool state)
{
    if (m_type != TrayIconWatcher::Bluetooth) {
        return;
    }

    if (state) {
        setIconSource("bluetooth-active-symbolic");
    } else {
        setIconSource("bluetooth-disabled-symbolic");
    }
}

void TrayIconWatcher::setVolume(int sound)
{
    setVolumeIconByValue(sound);
}

void TrayIconWatcher::setMuteState(bool state)
{
    //返回true表示是静音状态
    if (state) {
        setVolumeIconByValue(0);
    } else {
        getVolume();
    }
}
